#region
/* Adoor.NET - Object/Relational mapping framework leveraging ADO.NET`s powerful disconnected mode.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *  
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *  
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */ 
#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Adoor.Object.Disconnected;
using Adoor.Object.Domain;
using Adoor.Object.Entity;

namespace Adoor.Object.Relational
{	
	class ReferenceCollection<T>: ICollection<T>
	{
		protected List<object> keyList = new List<object>();
		protected ReferenceData referenceData;
		protected IEntity sourceEntity;

		public ReferenceCollection(ReferenceData referenceData, IEntity sourceEntity)
		{
			this.referenceData = referenceData;
			this.sourceEntity = sourceEntity;
			IReferenceReader reader = (IReferenceReader)this.referenceData.GetService(typeof(IReferenceReader));
			System.Collections.IEnumerator keys = reader.EnumTargetKeys(sourceEntity);
			while(keys.MoveNext())
			{
				//Debug.Assert(!keyList.Contains(keys.Current)); // TODO: always true ?
				object k = keys.Current;
				if(k is KeyValue)
				{
					keyList.Add(k.ToString());
				}
				else
					keyList.Add(keys.Current);
			}
		}

		public int Count
		{
			get { return this.keyList.Count; }
		}

		public void CopyTo(Array array, int index)
		{
			foreach(object o in this)
				array.SetValue(o, index++);
		}

		public bool IsSynchronized
		{
			get { return false; }
		}

		public object SyncRoot
		{
			get { return null; }
		}

		public IEnumerator<T> GetEnumerator()
		{
			IReferenceReader reader = (IReferenceReader)this.referenceData.GetService(typeof(IReferenceReader));
			reader.FetchTargetEntities(this.sourceEntity);
			return new ReferenceEnumerator<T>(this);
		}

		protected object GetEntity(KeyValue key)
		{
			DataSetObjectStorage storage = (DataSetObjectStorage)this.sourceEntity.State.Storage;
			ObjectContext context = storage.Context;
			object e;
			if (context != null)
				e = context.Find(this.referenceData.TargetData, ((IKeyMappingStrategy)this.referenceData.TargetData.OidData).GetOid(key));
			else
				e = storage.Find(this.referenceData.TargetData, ((IKeyMappingStrategy)this.referenceData.TargetData.OidData).GetOid(key));
			if (e == null)
				throw new Exception(string.Format("EntityCollection$EntityEnumerator.GetEntity: Entity {0}, row {1} not found", referenceData.TargetData.EntityType.Name, key));
			return e;
		}

        private class ReferenceEnumerator<T2> : IEnumerator<T2>
		{
            private ReferenceCollection<T2> collection;
			private System.Collections.IEnumerator keyEnumerator;

            public ReferenceEnumerator(ReferenceCollection<T2> collection)
			{
				this.collection = collection;
				this.keyEnumerator = collection.keyList.GetEnumerator();
            }

            #region IEnumerator<T2> Members

            T2 IEnumerator<T2>.Current
            {
                get { return (T2) this.collection.GetEntity(new KeyValue(this.keyEnumerator.Current)); }
            }

            #endregion

            #region IDisposable Members

            void IDisposable.Dispose()
            {
                
            }

            #endregion

            #region IEnumerator Members

            object System.Collections.IEnumerator.Current
            {
                get { return ((IEnumerator<T2>)this).Current; }
            }

            bool System.Collections.IEnumerator.MoveNext()
            {
                return keyEnumerator.MoveNext();
            }

            void System.Collections.IEnumerator.Reset()
            {
                keyEnumerator.MoveNext();
            }

            #endregion
        }

        #region ICollection<T> Members

        public virtual void Add(T item)
        {
            throw new NotSupportedException();
		}

        public virtual void Clear()
        {
            throw new NotSupportedException();
        }

        public bool Contains(T item)
        {
            if(item == null)
                return false;
            return keyList.Contains(item.ToString());
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            foreach (T o in this)
                array.SetValue(o, arrayIndex++);
        }

        public bool IsReadOnly
        {
            get { return true; }
        }

        public virtual bool Remove(T item)
        {
            throw new NotSupportedException();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            IReferenceReader reader = (IReferenceReader)this.referenceData.GetService(typeof(IReferenceReader));
            reader.FetchTargetEntities(this.sourceEntity);
            return (System.Collections.IEnumerator) new ReferenceEnumerator<T>(this);
        }

        #endregion
    }
    
	class ReferenceList<T>: ReferenceCollection<T>, IList<T>
	{
        private IReferenceWriter referenceFactory;
        private bool readOnly = false;

        internal ReferenceList(ReferenceData referenceData
            , IEntity sourceEntity)
			: base(referenceData, sourceEntity)
		{
            referenceFactory = (IReferenceWriter)this.referenceData.GetService(typeof(IReferenceWriter));
            readOnly = referenceFactory == null;
		}

        #region IList<T> Members

        public int IndexOf(T item)
        {
            return this.keyList.IndexOf(item.ToString());
        }

        public void Insert(int index, T item)
        {
            if (readOnly)
                throw new Exception("List is readonly");
            throw new NotSupportedException();
        }

        public void RemoveAt(int index)
        {
            if (readOnly)
                throw new Exception("List is readonly");
            this.referenceFactory.Remove(this.sourceEntity, (IEntity) this[index]);
            this.keyList.RemoveAt(index);
        }

        public T this[int index]
        {
            get
            {
                object kv = this.keyList[index];
                if (kv is KeyValue)
                    return (T) this.GetEntity((KeyValue)this.keyList[index]);
                else if (kv is String)
                    return (T) this.GetEntity(new KeyValue(this.keyList[index]));
                else
                    throw new Exception("invalid keyvalue");
            }
            set
            {
                if (readOnly)
                    throw new Exception("List is readonly");
                throw new NotSupportedException("The method or operation is not implemented.");
            }
        }

        #endregion

        #region IEnumerable Members

        public new System.Collections.IEnumerator GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        public override void Add(T item)
        {
            IObjectStorage storage = this.sourceEntity.State.Storage;
            Type entityType = storage.ObjectResolver.GetType(item);
            Type expectedType = this.referenceData.TargetData.EntityType;
            if (!expectedType.IsAssignableFrom(entityType))
                throw new Exception(String.Format("Wrong type : excepting {0}, found {1}", expectedType.Name, entityType.Name));

            this.referenceFactory.Add(this.sourceEntity, (IEntity)item, storage.CheckIntegrity);
            this.keyList.Add(item.ToString());
        }

        public override void Clear()
        {
            while (Count != 0)
            {
                RemoveAt(0);
            }
        }

        public override bool Remove(T item)
        {
            this.referenceFactory.Remove(this.sourceEntity, (IEntity)item);
            return this.keyList.Remove(item.ToString());
        }
    }
}
